#if HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#if HAVE_STDLIB_H 
#include <stdlib.h>
#endif  /* HAVE_STDLIB_H */

#if HAVE_STRING_H
#include <string.h>
#endif /* HAVE_STRING_H */

#if HAVE_STDIO_H
#include <stdio.h> 
#endif /* HAVE_STDIO_H */

#if HAVE_UNISTD_H
#include <unistd.h>
#endif  /* HAVE_UNISTD_H */

#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#if HAVE_SIGNAL_H
#include <signal.h>
#endif /* HAVE_SIGNAL_H */

#if HAVE_ERRNO_H
#include <errno.h>
#endif /* HAVE_ERRNO_H */

#include "a2-tgdb.h"
#include "fork_util.h"
#include "fs_util.h"
#include "pseudo.h"
#include "logger.h"
#include "io.h"
#include "state_machine.h"
#include "data.h"
#include "commands.h"
#include "globals.h"
#include "tgdb_types.h"
#include "queue.h"
#include "sys_util.h"
#include "ibuf.h"
#include "annotate_two.h"

static int 
a2_set_inferior_tty (void *ctx)
{
  struct annotate_two *a2 = (struct annotate_two *)ctx;
  if (!a2) {
    logger_write_pos (logger, __FILE__, __LINE__, "a2_set_inferior_tty error");
    return -1;
  }

    if ( commands_issue_command ( 
				a2->c, 
				a2->client_command_list,
				ANNOTATE_TTY, 
				pty_pair_get_slavename (a2->pty_pair), 
				0 ) == -1 ) {
        logger_write_pos ( logger, __FILE__, __LINE__, "commands_issue_command error");
        return -1;
    }

    return 0;
}


static int 
close_inferior_connection (void *ctx)
{
  struct annotate_two *a2 = (struct annotate_two *)ctx;
  if (!a2) {
    logger_write_pos (logger, __FILE__, __LINE__, "close_inferior_connection error");
    return -1;
  }

  if (a2->pty_pair)
    pty_pair_destroy (a2->pty_pair);

  return 0;
}

/* Here are the two functions that deal with getting tty information out
 * of the annotate_two subsystem.
 */

int a2_open_new_tty ( 
		void *ctx,
		int *inferior_stdin, 
		int *inferior_stdout ) {
	struct annotate_two *a2 = (struct annotate_two *)ctx;

  close_inferior_connection(a2);

  a2->pty_pair = pty_pair_create ();
  if (!a2->pty_pair) {
    logger_write_pos ( logger, __FILE__, __LINE__, "pty_pair_create failed");
    return -1;
  }

  *inferior_stdin = pty_pair_get_masterfd (a2->pty_pair);
  *inferior_stdout = pty_pair_get_masterfd (a2->pty_pair);

  a2_set_inferior_tty (a2);
    
  return 0;
}

const char *
a2_get_tty_name (void *ctx)
{
  struct annotate_two *a2 = (struct annotate_two *)ctx;
  if (!a2) {
    logger_write_pos ( logger, __FILE__, __LINE__, "a2_get_tty_name failed");
    return NULL;
  }

  return pty_pair_get_slavename (a2->pty_pair);
}

/* initialize_annotate_two
 *
 * initializes an annotate_two subsystem and sets up all initial values.
 */
static struct annotate_two *initialize_annotate_two ( void ) {
	struct annotate_two *a2 = (struct annotate_two *)
		cgdb_malloc ( sizeof ( struct annotate_two ) );

	a2->tgdb_initialized 	= 0;
	a2->debugger_stdin 		= -1;
	a2->debugger_out 		= -1;

	a2->pty_pair = NULL;

	/* null terminate */
	a2->config_dir[0] 		= '\0';
	a2->a2_gdb_init_file[0] = '\0';

	a2->source_already_received = 0;

	a2->cur_response_list = NULL;

	return a2;
}

/* tgdb_setup_config_file: 
 * -----------------------
 *  Creates a config file for the user.
 *
 *  Pre: The directory already has read/write permissions. This should have
 *       been checked by tgdb-base.
 *
 *  Return: 1 on success or 0 on error
 */
static int tgdb_setup_config_file ( struct annotate_two *a2, const char *dir ) {
    FILE *fp;

    strncpy ( a2->config_dir , dir , strlen ( dir ) + 1 );

    fs_util_get_path ( dir, "a2_gdb_init", a2->a2_gdb_init_file );

    if ( (fp = fopen ( a2->a2_gdb_init_file, "w" )) ) {
        fprintf( fp, 
            "set annotate 2\n"
            "set height 0\n");
        fclose( fp );
    } else {
        logger_write_pos ( logger, __FILE__, __LINE__, "fopen error '%s'", a2->a2_gdb_init_file);
        return 0;
    }

    return 1;
}

void* a2_create_context ( 
	const char *debugger, 
	int argc, char **argv,
	const char *config_dir,
    struct logger *logger) {
	
	struct annotate_two *a2 = initialize_annotate_two ();
    char a2_debug_file[FSUTIL_PATH_MAX];

    if ( !tgdb_setup_config_file( a2, config_dir ) ) {
        logger_write_pos ( logger, __FILE__, __LINE__, "tgdb_init_config_file error");
        return NULL;
    }

    /* Initialize the debug file that a2_tgdb writes to */
    fs_util_get_path ( config_dir, "a2_tgdb_debug.txt", a2_debug_file );
    io_debug_init(a2_debug_file);

    a2->debugger_pid = 
		invoke_debugger(
				debugger, argc, argv, 
				&a2->debugger_stdin, &a2->debugger_out, 
				0, a2->a2_gdb_init_file);

	/* Couldn't invoke process */
	if ( a2->debugger_pid == -1 )
		return NULL;

	return a2;
}

int a2_initialize ( 
	void *ctx,
	int *debugger_stdin, int *debugger_stdout,
	int *inferior_stdin, int *inferior_stdout) {
	struct annotate_two *a2 = (struct annotate_two *)ctx;

	*debugger_stdin 	= a2->debugger_stdin;
	*debugger_stdout 	= a2->debugger_out;

	a2->data 	= data_initialize ();
	a2->sm 	 	= state_machine_initialize ();
	a2->c 		= commands_initialize ();
	a2->g 		= globals_initialize ();
	a2->client_command_list = tgdb_list_init ();

	a2_open_new_tty ( a2, inferior_stdin, inferior_stdout );

   /* gdb may already have some breakpoints when it starts. This could happen
    * if the user puts breakpoints in there .gdbinit.
    * This makes sure that TGDB asks for the breakpoints on start up.
    */
    if ( commands_issue_command ( 
             a2->c,  
			 a2->client_command_list,
             ANNOTATE_INFO_BREAKPOINTS, NULL, 0 ) == -1 ) {
        logger_write_pos ( logger, __FILE__, __LINE__, "commands_issue_command error");
        return -1;
    }

   a2->tgdb_initialized = 1;

    return 0;
}

/* TODO: Implement shutdown properly */
int a2_shutdown ( void *ctx ) {
	struct annotate_two *a2 = (struct annotate_two *)ctx;
    cgdb_close(a2->debugger_stdin);

	data_shutdown ( a2->data );
	state_machine_shutdown ( a2->sm );
	commands_shutdown ( a2->c );
	globals_shutdown ( a2->g );
	return 0;
}

/* TODO: Implement error messages. */
int a2_err_msg ( void *ctx ) {
	return -1;
}

int a2_is_client_ready(void *ctx) {
	struct annotate_two *a2 = (struct annotate_two *)ctx;
    if ( !a2->tgdb_initialized )
        return 0;

    /* If the user is at the prompt */
    if ( data_get_state(a2->data) == USER_AT_PROMPT )
        return 1;

    return 0;
}

int a2_parse_io ( 
		void *ctx,
		const char *input_data, const size_t input_data_size,
		char *debugger_output, size_t *debugger_output_size,
		char *inferior_output, size_t *inferior_output_size,
		struct tgdb_list *list ) {
	int val;
	struct annotate_two *a2 = (struct annotate_two *)ctx;

	a2->command_finished = 0;
	a2->cur_response_list = list;

	val = a2_handle_data ( a2, a2->sm, input_data, input_data_size,
		debugger_output, debugger_output_size, list );

	a2->cur_response_list = NULL;

	if (a2->command_finished) 
		return 1;
	else 
		return 0;
}

struct tgdb_list *a2_get_client_commands ( void *ctx ) {
	struct annotate_two *a2 = (struct annotate_two *)ctx;
	return a2->client_command_list;
}

int 
a2_get_source_filename_pair (void *ctx, const char *file )
{
  struct annotate_two *a2 = (struct annotate_two *)ctx;
  int ret;

  ret = commands_issue_command (a2->c, a2->client_command_list, ANNOTATE_LIST,
				file, 0); 
  if (ret == -1)
  {
    logger_write_pos ( logger, __FILE__, __LINE__, "commands_issue_command error");
    return -1;
  }

  ret = commands_issue_command (a2->c, a2->client_command_list, 
				ANNOTATE_INFO_SOURCE_FILENAME_PAIR, file, 0);
  if (ret == -1)
  {
    logger_write_pos ( logger, __FILE__, __LINE__, "commands_issue_command error");
    return -1;
  }

  return 0;
}

int
a2_get_current_location (void *ctx, int on_startup)
{
  struct annotate_two *a2 = (struct annotate_two *)ctx;
  int ret;

  if (on_startup)
  {
    ret = commands_issue_command (a2->c, a2->client_command_list, ANNOTATE_LIST,
				  NULL, 0);
    if (ret == -1)
    {
      logger_write_pos (logger, __FILE__, __LINE__, 
			"commands_issue_command error");
      return -1;
    }
  }

  ret = commands_issue_command (a2->c, a2->client_command_list,
				ANNOTATE_INFO_LINE, NULL, 0);
  if (ret == -1)
  {
    logger_write_pos ( logger, __FILE__, __LINE__, "commands_issue_command error");
    return -1;
  }

  return 0;
}

int a2_get_inferior_sources ( void *ctx) {
	struct annotate_two *a2 = (struct annotate_two *)ctx;
    if ( commands_issue_command ( 
				a2->c, 
				a2->client_command_list,
				ANNOTATE_INFO_SOURCES, 
				NULL, 
				0 ) == -1 ) {
        logger_write_pos ( logger, __FILE__, __LINE__, "commands_issue_command error");
        return -1;
    }

	return 0;
}

char *
a2_return_client_command(void *ctx, enum tgdb_command_type c)
{
    char *ret = NULL;
    switch (c)
    {
        case TGDB_CONTINUE:
            ret = "continue";
            break;
        case TGDB_FINISH:
            ret = "finish";
            break;
        case TGDB_NEXT:
            ret = "next";
            break;
        case TGDB_START:
            ret = "start";
            break;
        case TGDB_RUN:
            ret = "run";
            break;
        case TGDB_KILL:
            ret = "kill";
            break;
        case TGDB_STEP:
            ret = "step";
            break;
        case TGDB_UP:
            ret = "up";
            break;
        case TGDB_DOWN:
            ret = "down";
            break;
        case TGDB_REQUEST_DISAS:
            ret = "disas";
            break;
        case TGDB_REQUEST_CALL_STACK:
            ret = "backtrace";
            break;
        case TGDB_REQUEST_THREADS:
            ret = "info threads";
            break;
        case TGDB_NEXTI:
            ret = "nexti";
            break;
        case TGDB_STEPI:
            ret = "stepi";
            break;
        case TGDB_SET_THREAD:
            ret = "thread %s";
            break;
        case TGDB_SET_FRAME:
            ret = "frame %s";
            break;
    }

    return ret;
}

char *a2_client_modify_breakpoint ( 
        void *ctx, 
        const char *file, 
        int line, 
        enum tgdb_breakpoint_action b ) {
    char *val = (char*)cgdb_malloc ( sizeof(char)* ( strlen(file) + 128 ) );

    if ( b == TGDB_BREAKPOINT_ADD ) {
        sprintf ( val, "break %s:%d", file, line );	
        return val;
    } else if ( b == TGDB_BREAKPOINT_DELETE ) {
        sprintf ( val, "clear %s:%d", file, line );	
        return val;
    } else if ( b == TGDB_TBREAKPOINT_ADD ) {
        sprintf ( val, "tbreak %s:%d", file, line );	
        return val;
    } else 
        return NULL;
}

pid_t a2_get_debugger_pid ( void *ctx ) {
    struct annotate_two *a2 = (struct annotate_two *)ctx;
    return a2->debugger_pid;
}

int a2_completion_callback(
        void *ctx,
        const char *command) {
    struct annotate_two *a2 = (struct annotate_two *)ctx;
    if ( commands_issue_command ( 
                a2->c, 
                a2->client_command_list,
                ANNOTATE_COMPLETE, command, 4 ) == -1 ) {
        logger_write_pos ( logger, __FILE__, __LINE__, "commands_issue_command error");
        return -1;
    }

    return 0;
}

int a2_user_ran_command ( void *ctx ) {
    struct annotate_two *a2 = (struct annotate_two *)ctx;
    return commands_user_ran_command ( a2->c, a2->client_command_list );
}

int a2_prepare_for_command ( void *ctx, struct tgdb_command *com ) {
    struct annotate_two *a2 = (struct annotate_two *)ctx;
    return commands_prepare_for_command ( a2, a2->c, com );
}

int a2_is_misc_prompt ( void *ctx ) {
    struct annotate_two *a2 = (struct annotate_two *)ctx;
    return globals_is_misc_prompt ( a2->g );
}
